package com.jdd.modules.sentrybox.controller;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jdd.common.api.vo.Result;
import com.jdd.common.aspect.annotation.*;
import com.jdd.common.constant.CacheConstant;
import com.jdd.common.constant.EnterAndExportTipsConstant;
import com.jdd.common.constant.JddConstant;
import com.jdd.common.system.vo.LoginUser;
import com.jdd.common.util.*;
import com.jdd.config.websocket.WebSocketServer;
import com.jdd.modules.configcartype.entity.ConfigCarType;
import com.jdd.modules.configcartype.service.IConfigCarTypeService;
import com.jdd.modules.operationLog.service.IParkHandOverLogService;
import com.jdd.modules.order.entity.ParkOrderInfo;
import com.jdd.modules.order.service.IParkOrderInfoService;
import com.jdd.modules.parkCarAbnormalRecord.entity.ParkCarAbnormalRecord;
import com.jdd.modules.parkCarAbnormalRecord.enums.AbnormalTypeEnums;
import com.jdd.modules.parkCarAbnormalRecord.service.IParkCarAbnormalRecordService;
import com.jdd.modules.parkcar.entity.ParkCarOutAbnormalLog;
import com.jdd.modules.parkcar.entity.ParkCarStored;
import com.jdd.modules.parkcar.mapper.ParkCarOutAbnormalLogMapper;
import com.jdd.modules.parkcar.mapper.ParkCarStoredMapper;
import com.jdd.modules.parking.entity.*;
import com.jdd.modules.parking.mapper.ParkArmInfoMapper;
import com.jdd.modules.parking.mapper.ParkCarEnterLogMapper;
import com.jdd.modules.parking.service.*;
import com.jdd.modules.sdk.LedCallInterface;
import com.jdd.modules.sdkidentifyresultpushlog.entity.*;
import com.jdd.modules.sdkidentifyresultpushlog.mapper.SdkIdentifyResultPushLogMapper;
import com.jdd.modules.sdkidentifyresultpushlog.service.ChargeFeeService;
import com.jdd.modules.sdkidentifyresultpushlog.service.ISdkIdentifyResultPushLogService;
import com.jdd.modules.sdkidentifyresultpushlog.service.ISdkIdentifyResultPushLogV3Service;
import com.jdd.modules.system.util.MoblieControllerUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @Description: 岗亭操作控制类
 * @author: ZhuLiuWei
 * @date: 2022/2/15 11:31
 */
@Slf4j
@RestController
@RequestMapping("/sentryBox/inParkingCar")
@Api(tags = "岗亭操作控制类")
public class InParkingCarController {

    @Resource
    private IInParkinglotCarService parkCarInfoService;
    @Resource
    private ParkArmInfoMapper parkArmInfoMapper;
    @Resource
    private ISdkIdentifyResultPushLogService sdkIdentifyResultPushLogService;
    @Resource
    private ParkCarOutAbnormalLogMapper parkCarOutAbnormalLogMapper;
    @Resource
    private LedCallInterface ledCallInterface;
    @Resource
    private ParkCarStoredMapper parkCarStoredMapper;
    @Resource
    private ParkCarEnterLogMapper parkCarEnterLogMapper;
    @Resource
    private IParkWorkStationService parkWorkStationService;
    @Resource
    private IParkOrderInfoService parkOrderInfoService;
    @Resource
    private IParkHandOverLogService parkHandOverLogService;
    @Resource
    private IParkArmInfoService parkArmInfoService;
    @Resource
    private IparkCarEnterLogService parkCarEnterLogService;
    @Resource
    private SdkIdentifyResultPushLogMapper sdkIdentifyResultPushLogMapper;
    @Resource
    private SimilarityRatioAlgorithm similarityRatioAlgorithm;
    @Resource
    private ChargeFeeService chargeFeeService;
    @Resource
    private CacheUtil cacheUtil;
    @Resource
    private ISdkIdentifyResultPushLogV3Service sdkIdentifyResultPushLogV3Service;
    @Resource
    private WebSocketServer socketServer;
    @Resource
    private IparkCarCancelOutLogService parkCarCancelOutLogService;
    @Resource
    private IConfigCarTypeService configCarTypeService;
    @Resource
    private IParkInfoService parkInfoService;
    @Resource
    private IParkCarAbnormalRecordService parkCarAbnormalRecordService;

    /**
     * 内部车辆修正车牌
     *
     * @param inParkingCar 内部车辆信息
     * @return 车牌修正结果
     */
    @AutoLog(value = "内部车辆修正车牌")
    @ApiOperation(value = "内部车辆修正车牌", notes = "内部车辆修正车牌")
    @PutMapping(value = "/editPlateNo")
    @OperationLogDetail(detail = "内部车辆修正车牌", level = LogLevelType.ONE, operationType = OperationType.UPDATE, operationUnit = OperationUnit.UNKNOWN)
    public Result<InParkinglotCar> editPlateNo(@RequestBody InParkinglotCar inParkingCar) {
        Result<InParkinglotCar> result = new Result<>();
        InParkinglotCar inParkinglotCar = parkCarInfoService.getById(inParkingCar.getId());
        if (ObjectUtils.isEmpty(inParkinglotCar)) {
            result.error500("未找到对应实体");
        } else {
            //根据车牌颜色转成对应车辆类型
            inParkingCar.setCarTypeNo(getCarTypeNo(inParkingCar.getPcColor()));
            inParkingCar.setBatchNumber(inParkinglotCar.getBatchNumber());
            if (parkCarInfoService.updateByBatchNumber(inParkingCar)) {
                result.success("车牌修正成功!");
            } else {
                result.error500("车牌修正失败!");
            }
        }
        return result;
    }

    /**
     * 补录入场记录并生成计费信息
     *
     * @param reChargeVo 入参对象
     * @return 补录结果信息
     */
    @AutoLog(value = "补录生成入场纪录，后端重新计费，然后返回计费信息")
    @ApiOperation(value = "补录生成入场纪录，后端重新计费，然后返回计费信息")
    @PostMapping(value = "/againChargeSaveEnterForCurrentOutCar")
    public Result<ParkOrderInfoVO> againChargeSaveEnterForCurrentOutCar(@RequestBody ReChargeVo reChargeVo) {
        try {
            //查询入口车道的信息
            ParkArmInfo parkInArmInfo = parkArmInfoMapper.getParkArmBySerialNo(reChargeVo.getSerialNo());
            if (ObjectUtils.isEmpty(parkInArmInfo)) {
                return Result.error("查询不到入口车道信息");
            }
            //查询出口车道的信息
            ParkArmInfo parkOutArmInfo = parkArmInfoMapper.getParkArmBySerialNo(reChargeVo.getOutSerialNo());
            if (ObjectUtils.isEmpty(parkInArmInfo)) {
                return Result.error("查询不到出口车道信息");
            }

            //补录入场纪录并上传到云端 补的入场记录序列号是出场的
            ParkCarEnterLog carEnterLog = sdkIdentifyResultPushLogService.createAbnormalParkCarEnterLogNew(parkInArmInfo, reChargeVo);

            if (ObjectUtils.isEmpty(carEnterLog)) {
                return Result.error("生成入场记录失败");
            }

            //返回计费信息
            Result<ParkOrderInfoVO> parkOrderInfoResult = sdkIdentifyResultPushLogService.getParkOrderInfoVOResult(reChargeVo);
            ParkOrderInfoVO result = parkOrderInfoResult.getResult();
            //时间设置
            if (JddConstant.IntNumber.INT_ZERO == (result.getChargeDuration())) {
                result.setChargeDuration(1);
            }

            //选择出口相机
            result.setIpAddr(parkOutArmInfo.getIpAddr());
            result.setSerialNo(parkOutArmInfo.getSerialNo());
            //要有个图片
            result.setOutImgPath(carEnterLog.getImgPath());
            result.setOssOutImgPath(carEnterLog.getOssImgPath());
            //如果是储值车，就直接扣费放行关弹窗
            if (isCarStored(reChargeVo, parkOutArmInfo, result, carEnterLog)) {
                //储值车匹配后处理
                return doParkCarStored(reChargeVo, carEnterLog, parkOutArmInfo, parkOrderInfoResult, result, JddConstant.AbnormalType.ADD_ENTER_HASFEE);
            } else {
                //非储值车补录直接添加一条异常,但是状态是未支付的，放行或者扫码变成已支付
                ParkCarOutAbnormalLog parkCarOutAbnormalLog = new ParkCarOutAbnormalLog();
                BeanUtils.copyProperties(carEnterLog, parkCarOutAbnormalLog);
                parkCarOutAbnormalLog.setCreateTime(new Date());
                parkCarOutAbnormalLog.setCarEnterId(carEnterLog.getId());
                parkCarOutAbnormalLog.setIpAddr(parkOutArmInfo.getIpAddr());
                parkCarOutAbnormalLog.setOrderId(result.getId());
                parkCarOutAbnormalLog.setPayStatus(JddConstant.OrderConstant.ORDER_PAY_STATE_UNPAID);
                parkCarOutAbnormalLogMapper.insert(parkCarOutAbnormalLog);
                //补录后，播报钱数，扫码放行
                ledCallInterface.OutShowFeeAll(parkOutArmInfo.getSerialNo(), reChargeVo.getPlateNo(), result);
            }

            String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkInArmInfo);
            Map<String, Object> data = parkArmInfoService.getData(workUser);
            socketServer.sendMessageAll(JSON.toJSONString(data));
            //如果出场是白名单，再次识别下就行不用处理
            parkOrderInfoResult.setResult(result);
            return parkOrderInfoResult;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return Result.error("补录生成入场纪录异常！");
        }
    }

    /**
     * 手动入场
     *
     * @param reChargeVo 入参对象
     * @return 入场结果信息
     */
    @AutoLog(value = "手动入场")
    @ApiOperation(value = "手动入场")
    @PostMapping(value = "/manualOpenedSaveParkCarEnterLog")
    public Result<String> manualOpenedSaveParkCarEnterLog(@RequestBody ReChargeVo reChargeVo) {
        try {
            String serialNo = reChargeVo.getSerialNo();
            //查询入口车道的信息
            ParkArmInfo parkArmInfo = parkArmInfoMapper.getParkArmBySerialNo(serialNo);
            if (ObjectUtils.isEmpty(parkArmInfo)) {
                return Result.error("查询不到入口车道信息");
            }

            ParkCarEnterLog parkCarEnterLog = getParkCarEnterLog(parkArmInfo, reChargeVo);
            //保存入场记录
            parkCarEnterLogService.save(parkCarEnterLog);
            //允许入场 语音播报 并发送记录到云端
            sdkIdentifyResultPushLogService.openedSaveParkCarEnterLog(reChargeVo, parkCarEnterLog);
            String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
            Map<String, Object> data = parkArmInfoService.getData(workUser);
            socketServer.sendMessageAll(JSON.toJSONString(data));
            return Result.ok("入场成功！");
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return Result.error("入场异常！");
        }
    }

    /**
     * 确认开闸
     *
     * @param reChargeVo 入参对象
     * @return 入场结果信息
     */
    @AutoLog(value = "确认开闸 允许 保存入场记录")
    @ApiOperation(value = "确认开闸 允许 保存入场记录")
    @PostMapping(value = "/openedSaveParkCarEnterLog")
    public Result<String> openedSaveParkCarEnterLog(@RequestBody ReChargeVo reChargeVo) {
        try {
            String serialNo = reChargeVo.getSerialNo();
            //查询入口车道的信息
            ParkArmInfo parkArmInfo = parkArmInfoMapper.getParkArmBySerialNo(serialNo);
            if (ObjectUtils.isEmpty(parkArmInfo)) {
                return Result.error("查询不到入口车道信息");
            }

            QueryWrapper<SdkIdentifyResultPushLog> wrapper = new QueryWrapper<>();
            if (StringUtils.isNotBlank(serialNo)) {
                wrapper.eq("serial_no", serialNo);
            }
            if (EnterAndExportTipsConstant.UNLICENSED_CAR.equals(reChargeVo.getPlateNo())) {
                wrapper.like("license", "无");
            } else {
                wrapper.eq("license", reChargeVo.getPlateNo());
            }
            wrapper.orderByDesc("create_time");
            wrapper.last("limit 1");
            List<SdkIdentifyResultPushLog> sdkIdentifyList = sdkIdentifyResultPushLogService.list(wrapper);
            if (ObjectUtils.isEmpty(sdkIdentifyList)) {
                return Result.error("查询不到sdk识别结果，入场异常！");
            }

            reChargeVo.setEnterDate(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            ParkCarEnterLog parkCarEnterLog = parkArmInfoService.getParkCarEnterLog(parkArmInfo, reChargeVo, sdkIdentifyList.get(0));
            parkCarEnterLog.setEnterType(JddConstant.IntNumber.INT_ZERO);
            parkCarEnterLog.setIsTransfer(JddConstant.StringNumber.STRING_ZERO);
            //加上工作站名和岗亭人账户
            String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
            if (ObjectUtils.isNotEmpty(workUser)) {
                parkCarEnterLog.setWorkName(parkArmInfo.getWorkStationName());
                parkCarEnterLog.setSysUserName(workUser);
            }

            String openId = cacheUtil.get(CacheConstant.PARK_CAR_ENTER_OPEN_ID, parkArmInfo.getSerialNo());
            if (openId != null && EnterAndExportTipsConstant.UNLICENSED_CAR.equals(reChargeVo.getPlateNo())) {
                parkCarEnterLog.setOpenid(openId);
                cacheUtil.delete(CacheConstant.PARK_CAR_ENTER_OPEN_ID, parkArmInfo.getSerialNo());
            }

            //保存入场记录
            parkCarEnterLogService.save(parkCarEnterLog);
            //允许入场 语音播报 并发送记录到云端
            sdkIdentifyResultPushLogService.openedSaveParkCarEnterLog(reChargeVo, parkCarEnterLog);
            Map<String, Object> data = parkArmInfoService.getData(workUser);
            socketServer.sendMessageAll(JSON.toJSONString(data));
            return Result.ok("入场成功！");
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return Result.error("入场异常！");
        }
    }

    /**
     * 拒绝放行
     *
     * @param reChargeVo 入参对象
     * @return 拒绝放行结果
     */
    @AutoLog(value = "确认开闸 拒绝 语音播报")
    @ApiOperation(value = "确认开闸 拒绝 语音播报")
    @PostMapping(value = "/refuseCarEnter")
    public Result<String> refuseCarEnter(@RequestBody ReChargeVo reChargeVo) {
        try {
            sdkIdentifyResultPushLogService.refuseCarEnter(reChargeVo);
            return Result.ok("操作成功");
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return Result.error("操作失败");
        }
    }

    /**
     * 无入场记录模糊匹配
     *
     * @param plateNo   车牌号
     * @param armName   入场车道
     * @param carTypeId 车辆类型ID
     * @param timeStart 入场开始时间
     * @param timeEnd   入场结束时间
     * @return 模糊匹配结果
     */
    @AutoLog(value = "当相机入场识别错误，车辆出场时，自动查询可能是当前出场车辆的入场记录")
    @ApiOperation(value = "当相机入场识别错误，车辆出场时，自动查询可能是当前出场车辆的入场记录")
    @GetMapping(value = "/autoMatchingEnterLogForCurrentOutCar")
    public Result<Page<SimilarityRatioParkCarEnterLog>> autoMatchingEnterLogForCurrentOutCar(@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
                                                                                             @RequestParam(name = "pageSize", defaultValue = "4") Integer pageSize,
                                                                                             @RequestParam(name = "plateNo") String plateNo,
                                                                                             @RequestParam(name = "armName", required = false) String armName,
                                                                                             @RequestParam(name = "serialNo", required = false) String serialNo,
                                                                                             @RequestParam(name = "carTypeId", required = false) String carTypeId,
                                                                                             @RequestParam(name = "timeStart", required = false) String timeStart,
                                                                                             @RequestParam(name = "timeEnd", required = false) String timeEnd) {

        QueryWrapper<ParkInfo> queryWrapper = new QueryWrapper<>();
        ParkInfo parkInfo = parkInfoService.getOne(queryWrapper);
        String parkCode = parkInfo.getParkCode();

        //最小保留匹配度
        float minSimilarityRatio = 50.0f;

        Result<Page<SimilarityRatioParkCarEnterLog>> result = new Result<>();
        //创建集合用于模糊查询条件
        List<String> plateStrList = new ArrayList<>();

        //将当前车牌字符串拆分成数组，并按顺序将相邻的三位数组元素组成字符串添加到集合中
        char[] plateNoChars = plateNo.toUpperCase().toCharArray();
        //合并添加到集合中
        for (int i = 0; i < plateNoChars.length - 4; i++) {
            String plateStr = String.valueOf(plateNoChars[i]) +
                    plateNoChars[i + 1] +
                    plateNoChars[i + 2]+ plateNoChars[i + 3]+ plateNoChars[i + 4];
            plateStrList.add(plateStr);
        }
        log.info("车牌拆分结果：" + CollUtil.join(plateStrList, "、"));

        //根据集合中的字符串进行多次数据库模糊查询
        List<ParkCarEnterLog> enterLogLikeList = sdkIdentifyResultPushLogMapper.getParkCarEnterLogLikeList(plateStrList, parkCode, armName, serialNo, carTypeId, timeStart, timeEnd);

        //未匹配到入场数据
        if (ObjectUtils.isEmpty(enterLogLikeList)) {
            result.setResult(new Page<>());
            result.setSuccess(true);
            return result;
        }

        //遍历查询结果，得到匹配车牌号，根据相似度算法获得与当前出场车牌的相似度
        List<SimilarityRatioParkCarEnterLog> similarityCarEnterLogs = new ArrayList<>();
        for (ParkCarEnterLog parkCarEnterLog : enterLogLikeList) {
            //获取相似度(0-100) 结果100则为完全一样
            int similarityRatio;
            if (parkCarEnterLog.getPlateNo().contains(plateNo)) {
                similarityRatio = 100;
            } else {
                similarityRatio = (int) similarityRatioAlgorithm.getSimilarityRatio(plateNo, parkCarEnterLog.getPlateNo());
            }

            //相似度小于50直接抛弃该数据
            if (similarityRatio > minSimilarityRatio) {
                SimilarityRatioParkCarEnterLog similarityCarEnterLog = new SimilarityRatioParkCarEnterLog();
                BeanUtils.copyProperties(parkCarEnterLog, similarityCarEnterLog);
                similarityCarEnterLog.setSimilarityRatio(similarityRatio);
                similarityCarEnterLogs.add(similarityCarEnterLog);
            }
        }

        //根据相似度进行降序排序
        similarityCarEnterLogs.sort((o1, o2) -> (int) (o2.getSimilarityRatio() - o1.getSimilarityRatio()));

        //转换成分页
        Page<SimilarityRatioParkCarEnterLog> page = new Page<>(pageNo, pageSize);
        int listSize = similarityCarEnterLogs.size();
        //总条数
        page.setTotal(listSize);
        //有多少页
        if (listSize % pageSize > 0) {
            page.setPages(listSize / pageSize + 1L);
        } else {
            page.setPages(listSize / pageSize);
        }

        similarityCarEnterLogs = similarityCarEnterLogs.subList(Math.min((pageNo - 1) * pageSize, listSize), Math.min(pageNo * pageSize, listSize));
        //当前显示的数据
        page.setRecords(similarityCarEnterLogs);

        result.setResult(page);
        result.setSuccess(true);
        return result;
    }

    /**
     * 模糊匹配后并重新计费
     *
     * @param reChargeVo 入参对象
     * @return 计费结果信息
     */
    @AutoLog(value = "模糊匹配后，前端发送出口信息，后端重新计费，然后返回计费信息")
    @ApiOperation(value = "模糊匹配后，前端发送出口信息，后端重新计费，然后返回计费信息")
    @PostMapping(value = "/againChargeForCurrentOutCar")
    public Result<ParkOrderInfoVO> againChargeForCurrentOutCar(@RequestBody ReChargeVo reChargeVo) {
        try {
            //查询入场纪录
            List<ParkCarEnterLog> parkCarEnterLogs = chargeFeeService.getParkCarEnterLog(reChargeVo.getPlateNo(), null, reChargeVo.getParkCode());
            if (CollUtil.isEmpty(parkCarEnterLogs) || null == parkCarEnterLogs.get(0)) {
                return Result.error("查询不到入场信息");
            }
            //得到车道信息
            ParkArmInfo parkArmInfo = parkArmInfoMapper.getParkArmBySerialNo(reChargeVo.getOutSerialNo());
            if (ObjectUtils.isEmpty(parkArmInfo)) {
                return Result.error("查询不到出口车道信息");
            }

            ParkCarEnterLog parkCarEnterLog = parkCarEnterLogs.get(0);
            //生成订单信息保存数据库上传云端后返回前端
            Result<ParkOrderInfoVO> parkOrderInfoResult = sdkIdentifyResultPushLogService.getParkOrderInfoVOResult(reChargeVo);
            ParkOrderInfoVO park = parkOrderInfoResult.getResult();
            //选择出口相机
            park.setIpAddr(parkArmInfo.getIpAddr());
            park.setSerialNo(parkArmInfo.getSerialNo());
            //区分是否模糊匹配
            park.setVersion(JddConstant.IntNumber.INT_ONE);
            //订单信息车牌改成出场
//            park.setPlateNo(reChargeVo.getOutPlateNo());
            //补录的要有个图片
            park.setOutImgPath(parkCarEnterLog.getImgPath());
            park.setOssOutImgPath(parkCarEnterLog.getOssImgPath());
            //停车场入口名称
            park.setEnterArmName(parkCarEnterLog.getArmName());
            //如果是储值车，就直接扣费放行关弹窗
            if (isCarStored(reChargeVo, parkArmInfo, park, parkCarEnterLog)) {
                //储值车匹配后处理
                return doParkCarStored(reChargeVo, parkCarEnterLog, parkArmInfo, parkOrderInfoResult, park, JddConstant.AbnormalType.LIKE_ENTER_HASFEE);
            } else {
                String outPlateNo = reChargeVo.getOutPlateNo();
                String plateNo = reChargeVo.getPlateNo();
                cacheUtil.put("truePlateNo", plateNo, outPlateNo);
                //匹配后，播报钱数，扫码放行
                ledCallInterface.OutShowFeeAll(parkArmInfo.getSerialNo(), reChargeVo.getOutPlateNo(), park);
            }
            //如果出场是白名单，再次识别下就行不用处理
            parkOrderInfoResult.setResult(park);
            return parkOrderInfoResult;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return Result.error("模糊匹配生成纪录异常！");
        }
    }

    /**
     * 当入场相机识别错误，当前车辆出场时
     * 车场管理员选中一条入场记录，此入场记录将匹配当前出场车辆，并重新执行出场逻辑
     *
     * @param parkCarEnterLogDTO 入参对象
     * @return 出场结果信息
     */
    @AutoLog(value = "当相机入场识别错误时，为当前出场车辆匹配一条入场记录，并重新执行出场逻辑")
    @ApiOperation(value = "当相机入场识别错误时，为当前出场车辆匹配一条入场记录，并重新执行出场逻辑")
    @PostMapping(value = "/matchingEnterLogForCurrentOutCar")
    public Result<Object> matchingEnterLogForCurrentOutCar(@RequestBody ParkCarEnterLogDTO parkCarEnterLogDTO) {
        Result<Object> result = new Result<>();
        //当前出场相机序列号
        String serialNo = parkCarEnterLogDTO.getOutCameraSerialNo();
        //当前出场车辆车牌号
        String plateNo = parkCarEnterLogDTO.getOutCarPlateNo();

        ParkCarEnterLog parkCarEnterLog = new ParkCarEnterLog();
        BeanUtils.copyProperties(parkCarEnterLogDTO, parkCarEnterLog);

        if (StringUtils.isEmpty(serialNo)) {
            result.error500("设备序列号为空，无法匹配入场记录");
            return result;
        }

        //通过车牌号和设备序列号拿到最近一条相机sdk识别纪录
        SdkIdentifyResultPushLog lastTimeSdkIdentifyResultPushLogByPlate = sdkIdentifyResultPushLogMapper.getLastTimeSdkIdentifyResultPushLogByPlate(plateNo, serialNo);
        //通过序列号查询车辆出入口信息
        QueryWrapper<ParkArmInfo> parkArmInfoQueryWrapper = new QueryWrapper<>();
        parkArmInfoQueryWrapper.eq("serial_no", serialNo);
        ParkArmInfo parkArmInfo = parkArmInfoService.getOne(parkArmInfoQueryWrapper);
        //为当前车辆匹配入场信息
        parkCarEnterLog.setPlateNo(plateNo);
        parkCarEnterLog.setId(null);
        parkCarEnterLog.setEnterType(JddConstant.IntNumber.INT_TWO);
        parkCarEnterLog.setRemark("入场时识别为" + parkCarEnterLogDTO.getPlateNo());
        parkCarEnterLogService.save(parkCarEnterLog);

        //缓存当前车牌
        String ossImgPath = lastTimeSdkIdentifyResultPushLogByPlate.getOssImg();
        String localImgPath = lastTimeSdkIdentifyResultPushLogByPlate.getImg();
        String armName = parkArmInfo.getArmName();
        cacheUtil.put(CacheConstant.CURRENT_EXIT_LICENSE_PLATE, serialNo, plateNo + "#" + ossImgPath + "#" + localImgPath + "#" + armName);
        log.info("重新匹配入场信息的出口缓存:SerialNo:{}>>>>>>>>>plateNo:{}", serialNo, plateNo);

        //根据sdk识别结果再次调用出场逻辑
        try {
            sdkIdentifyResultPushLogV3Service.identifyJudge(lastTimeSdkIdentifyResultPushLogByPlate, parkArmInfo);
            result.setSuccess(true);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            result.error500("出场失败");
        }
        return result;
    }

    /**
     * 取消出场
     *
     * @param parkCarEnterLogDTO 取消出场入参
     * @return 执行结果信息
     */
    @AutoLog(value = "取消出场")
    @ApiOperation(value = "取消出场")
    @PostMapping(value = "/cancelCurrentCarOut")
    public Result<Object> cancelCurrentCarOut(@RequestBody ParkCarEnterLogDTO parkCarEnterLogDTO) {

        Result<Object> result = new Result<>();
        //车牌号
        String plateNo = parkCarEnterLogDTO.getPlateNo();
        //设备序列号
        String serialNo = parkCarEnterLogDTO.getSerialNo();

        LoginUser loginUser = LoginUserUtils.getLoginUser();
        if (Objects.isNull(loginUser)) {
            result.error500("请登录后操作!");
            return result;
        }

        if (StringUtils.isEmpty(plateNo)) {
            result.error500("车牌号为空!");
            return result;
        }

        if (StringUtils.isEmpty(serialNo)) {
            result.error500("设备序列号为空，无法匹配入场记录");
            return result;
        }

        //通过序列号查询车辆出入口信息
        QueryWrapper<ParkArmInfo> parkArmInfoQueryWrapper = new QueryWrapper<>();
        parkArmInfoQueryWrapper.eq("serial_no", serialNo);
        ParkArmInfo parkArmInfo = parkArmInfoService.getOne(parkArmInfoQueryWrapper);

        if (ObjectUtils.isEmpty(parkArmInfo)) {
            result.error500("查询不到出口车道信息");
            return result;
        }

        ParkCarCancelOutLog cancelOutLog = new ParkCarCancelOutLog();
        ParkCarEnterLog lastCarEnterLog = parkCarEnterLogService.getLastCarEnterLog(parkArmInfo.getParkCode(), plateNo, JddConstant.ParkCarEnterLog.CAR_NO_OUT);
        ParkCarAbnormalRecord parkCarAbnormalRecord = new ParkCarAbnormalRecord();
        if (ObjectUtils.isNotEmpty(lastCarEnterLog)) {
            cancelOutLog.setCarEnterId(lastCarEnterLog.getId());
            cancelOutLog.setCarNature(lastCarEnterLog.getCarNature());
            cancelOutLog.setCarType(lastCarEnterLog.getCarType());
            parkCarAbnormalRecord.setEnterBatchNumber(lastCarEnterLog.getBatchNumber());
            parkCarAbnormalRecord.setCarType(lastCarEnterLog.getCarType());
            parkCarAbnormalRecord.setCarNature(lastCarEnterLog.getCarNature());
            parkCarAbnormalRecord.setIdentifyImgPath(lastCarEnterLog.getImgPath());
        }
        cancelOutLog.setId(SnowFlakeUtil.getId());
        cancelOutLog.setParkCode(parkArmInfo.getParkCode());
        cancelOutLog.setParkName(parkArmInfo.getParkName());
        cancelOutLog.setPlateNo(plateNo);
        cancelOutLog.setSerialNo(serialNo);
        cancelOutLog.setArmName(parkArmInfo.getArmName());
        cancelOutLog.setIpAddr(parkArmInfo.getIpAddr());
        Date nowDate = new Date();
        cancelOutLog.setCreateTime(nowDate);
        cancelOutLog.setSynchronousStatus(JddConstant.StatusType.STATUS_N);
        cancelOutLog.setRemark(parkCarEnterLogDTO.getRemark());
        //取消出场新增异常出场纪录到park_car_abnormal_record
        parkCarAbnormalRecord.setId(SnowFlakeUtil.getId());
        parkCarAbnormalRecord.setParkCode(parkArmInfo.getParkCode());
        parkCarAbnormalRecord.setParkName(parkArmInfo.getParkName());
        parkCarAbnormalRecord.setPlateNo(plateNo);
        parkCarAbnormalRecord.setSerialNo(serialNo);
        parkCarAbnormalRecord.setArmName(parkArmInfo.getArmName());
        parkCarAbnormalRecord.setTriggerTime(nowDate);
        parkCarAbnormalRecord.setAbnormalType(AbnormalTypeEnums.CANCLE_OUT.getCode());
        parkCarAbnormalRecord.setRemark(parkCarEnterLogDTO.getRemark());
        parkCarAbnormalRecord.setCreateTime(nowDate);
        parkCarAbnormalRecord.setCreateBy(loginUser.getRealname());
        parkCarAbnormalRecord.setDeleted(0);
        //加上工作站名和岗亭人账户
        String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
        if (ObjectUtils.isNotEmpty(workUser)) {
            cancelOutLog.setWorkName(parkArmInfo.getWorkStationName());
            cancelOutLog.setSysUserName(workUser);
            parkCarAbnormalRecord.setWorkStationName(parkArmInfo.getWorkStationName());
            parkCarAbnormalRecord.setWorkStationOperator(workUser);
        }
        parkCarCancelOutLogService.save(cancelOutLog);
        parkCarAbnormalRecordService.save(parkCarAbnormalRecord);
        return result;
    }

    /**
     * 功能描述: 补录与匹配判断是否储值，储值就直接缴费放行
     *
     * @param reChargeVo  前端发来的数据
     * @param parkArmInfo 相机信息
     * @param result      订单信息
     * @param carEnterLog 入场纪录
     * @Author: lcy
     * @Date: 2021/9/29
     */
    private boolean isCarStored(ReChargeVo reChargeVo, ParkArmInfo parkArmInfo, ParkOrderInfoVO result, ParkCarEnterLog carEnterLog) {
        String plateNo = null == reChargeVo.getOutPlateNo() ? reChargeVo.getPlateNo() : reChargeVo.getOutPlateNo();
        //判断是否储值车
        ParkCarStored parkCarStored = parkCarStoredMapper.getEntranceStored(plateNo, "Y");
        if (null != parkCarStored) {
            //余额
            Integer payFee = parkCarStored.getPayFee();
            //本次金额
            Integer unPayFee = Integer.parseInt(result.getChargeUnpaid()) / 100;
            if (payFee >= unPayFee) {
                log.info("储值车:{},余额大于本次费用-----余额:{},本次费用:{}", plateNo, payFee, unPayFee);
                //支付费用-本次金额=剩余金额
                int remaining = payFee - unPayFee;
                log.info("储值车出场:{},支付后余额为:{}", plateNo, remaining);
                ledCallInterface.OutStoredPayFeeAll(reChargeVo.getOutSerialNo(), plateNo, unPayFee, remaining, result);

                //保存订单
                ParkOrderInfo orderInfo = getParkOrderInfo(reChargeVo, parkArmInfo, result, plateNo);
                //加上工作站名和岗亭人账户
                String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
                if (ObjectUtils.isNotEmpty(workUser)) {
                    orderInfo.setWorkName(parkArmInfo.getWorkStationName());
                    orderInfo.setSysUserName(workUser);
                }
                parkOrderInfoService.save(orderInfo);

                //出场记录
                ParkCarOutLog parkCarOutLog = getParkCarOutLog(reChargeVo, parkArmInfo, carEnterLog, plateNo);
                try {
                    //加上工作站名和岗亭人账户
                    if (ObjectUtils.isNotEmpty(workUser)) {
                        parkCarOutLog.setWorkName(parkArmInfo.getWorkStationName());
                        parkCarOutLog.setSysUserName(workUser);
                        parkHandOverLogService.updateHandOverAuto(parkCarOutLog, orderInfo, workUser, 1);
                    }
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                    log.error("车辆出场交接班更新或者发送场内车辆详情失败！");
                }
                //更新储值车余额
                parkCarStored.setPayFee(remaining);
                parkCarStoredMapper.updateById(parkCarStored);
                return true;
            }
        }
        return false;
    }

    /**
     * 组装出场数据
     *
     * @param reChargeVo  入参对象
     * @param parkArmInfo 车道信息
     * @param carEnterLog 入场记录
     * @param plateNo     车牌号
     * @return 出场数据
     */
    private ParkCarOutLog getParkCarOutLog(ReChargeVo reChargeVo, ParkArmInfo parkArmInfo, ParkCarEnterLog carEnterLog, String plateNo) {
        ParkCarOutLog parkCarOutLog = new ParkCarOutLog();
        parkCarOutLog.setId(SnowFlakeUtil.getId());
        parkCarOutLog.setParkCode(parkArmInfo.getParkCode());
        parkCarOutLog.setParkName(parkArmInfo.getParkName());
        parkCarOutLog.setPlateNo(plateNo);
        parkCarOutLog.setCarEnterId(null != carEnterLog ? carEnterLog.getId() : null);
        parkCarOutLog.setSerialNo(reChargeVo.getOutSerialNo());
        parkCarOutLog.setCarNature(JddConstant.MqttRecResultPushLog.IS_WHITE_TWO);
        parkCarOutLog.setCarType(String.valueOf(JddConstant.IntNumber.INT_ONE));
        parkCarOutLog.setCarOutTime(DateUtil.getTime());
        parkCarOutLog.setArmName(parkArmInfo.getArmName());
        parkCarOutLog.setImgPath(null != carEnterLog ? carEnterLog.getImgPath() : null);
        parkCarOutLog.setOssImgPath(null != carEnterLog ? carEnterLog.getOssImgPath() : null);
        parkCarOutLog.setIpAddr(parkArmInfo.getIpAddr());
        parkCarOutLog.setCreateTime(new Date());
        parkCarOutLog.setSynchronousStatus(JddConstant.StatusType.STATUS_N);
        return parkCarOutLog;
    }

    /**
     * 组装订单数据
     *
     * @param reChargeVo  入参对象
     * @param parkArmInfo 车道信息
     * @param result      订单信息
     * @param plateNo     车牌号
     * @return 订单数据
     */
    private ParkOrderInfo getParkOrderInfo(ReChargeVo reChargeVo, ParkArmInfo parkArmInfo, ParkOrderInfoVO result, String plateNo) {
        ParkOrderInfo orderInfo = new ParkOrderInfo();
        orderInfo.setCarEnterId(result.getCarEnterId());
        orderInfo.setEnterTime(result.getEnterTime());
        orderInfo.setChargeTotalFee(result.getChargeUnpaid());
        orderInfo.setOutTime(DateUtils.formatDateTime());
        orderInfo.setId(SnowFlakeUtil.getId());
        orderInfo.setCreateTime(new Date());
        orderInfo.setParkCode(reChargeVo.getParkCode());
        orderInfo.setParkName(parkArmInfo.getParkName());
        orderInfo.setSerialNo(reChargeVo.getOutSerialNo());
        orderInfo.setPlateNo(plateNo);
        orderInfo.setPayTime(DateUtils.formatDateTime());
        orderInfo.setChargeDuration(result.getChargeDuration());
        orderInfo.setCarType("1");
        orderInfo.setChargeUnpaid(result.getChargeUnpaid());
        orderInfo.setChargeDue(result.getChargeUnpaid());
        orderInfo.setChargePaid(String.valueOf(result.getChargeUnpaid()));
        orderInfo.setOrderStatus(JddConstant.OrderConstant.ORDER_PAY_STATE_SUCCESS);
        orderInfo.setPayType("5");
        return orderInfo;
    }

    /**
     * 功能描述: 储值车处理
     *
     * @param reChargeVo          前端发送实体
     * @param parkCarEnterLog     入场纪录
     * @param parkArmInfo         相机信息
     * @param parkOrderInfoResult 生成的订单信息的返回值
     * @param park                生成的订单信息
     * @Author: lcy
     * @Date: 2021/9/29
     */
    private Result<ParkOrderInfoVO> doParkCarStored(ReChargeVo reChargeVo, ParkCarEnterLog parkCarEnterLog, ParkArmInfo parkArmInfo, Result<ParkOrderInfoVO> parkOrderInfoResult, ParkOrderInfoVO park, String type) throws IOException {
        String plateNo = null == reChargeVo.getOutPlateNo() ? reChargeVo.getPlateNo() : reChargeVo.getOutPlateNo();
        //储值车补录与匹配都直接添加一条异常,但是状态是已支付的
        ParkCarOutAbnormalLog parkCarOutAbnormalLog = new ParkCarOutAbnormalLog();
        BeanUtils.copyProperties(parkCarEnterLog, parkCarOutAbnormalLog);
        parkCarOutAbnormalLog.setId(SnowFlakeUtil.getId());
        parkCarOutAbnormalLog.setPlateNo(plateNo);
        parkCarOutAbnormalLog.setCreateTime(new Date());
        parkCarOutAbnormalLog.setCarEnterId(parkCarEnterLog.getId());
        parkCarOutAbnormalLog.setIpAddr(parkArmInfo.getIpAddr());
        parkCarOutAbnormalLog.setOrderId(park.getId());
        parkCarOutAbnormalLog.setPayStatus(JddConstant.OrderConstant.ORDER_PAY_STATE_SUCCESS);
        parkCarOutAbnormalLog.setRemark(JddConstant.AbnormalType.ADD_ENTER_HASFEE.equals(type) ? "储值车补录入场纪录" : "储值车匹配入场纪录");
        parkCarOutAbnormalLogMapper.insert(parkCarOutAbnormalLog);
        //入场纪录设置为已出场
        parkCarEnterLogMapper.updateIsOut(plateNo);
        //储值车补录或者匹配则通过return的订单的支付状态决定是否关闭框
        park.setOrderStatus(JddConstant.OrderConstant.ORDER_PAY_STATE_SUCCESS);
        //监控左上角显示的数据
        JSONObject successOrder = new JSONObject();
        successOrder.put("successOrder", park);
        socketServer.sendInfo(successOrder.toJSONString(), parkArmInfo.getUsedUserId());
        return parkOrderInfoResult;
    }

    /**
     * 获得车辆性质
     *
     * @param strColor 车牌颜色
     * @return 车辆性质
     */
    public String getCarTypeNo(String strColor) {
        String carTypeNo = "1";
        switch (strColor) {
            case "蓝色":
                carTypeNo = "1";
                break;
            case "黄色":
                carTypeNo = "3";
                break;
            case "白色":
                carTypeNo = "5";
                break;
            case "绿色":
            case "黄绿色":
                carTypeNo = "19";
                break;
            case "黑色":
                carTypeNo = "10";
                break;
            default:
                break;
        }
        return carTypeNo;
    }

    /**
     * 生成入场记录
     *
     * @param parkArmInfo 车场信息
     * @param reChargeVo  入参信息
     * @return 入场记录信息
     */
    public ParkCarEnterLog getParkCarEnterLog(ParkArmInfo parkArmInfo, ReChargeVo reChargeVo) {
        ParkCarEnterLog carEnterLog = new ParkCarEnterLog();
        carEnterLog.setId(SnowFlakeUtil.getId());
        carEnterLog.setPlateNo(reChargeVo.getPlateNo());
        carEnterLog.setParkCode(parkArmInfo.getParkCode());
        carEnterLog.setParkName(parkArmInfo.getParkName());
        carEnterLog.setSerialNo(parkArmInfo.getSerialNo());
        carEnterLog.setArmName(parkArmInfo.getArmName());
        //手动入场
        carEnterLog.setEnterType(JddConstant.IntNumber.INT_TWO);
        carEnterLog.setIsTransfer(JddConstant.StringNumber.STRING_ZERO);
        //开闸原因
        carEnterLog.setRemark("开闸原因：" + reChargeVo.getRemark());
        carEnterLog.setCarTypeNo(getCarTypeNo(reChargeVo.getPcColor()));
        //前端传来的区域id，用户自行选择的区域id
        if (StringUtils.isNotBlank(parkArmInfo.getTargetAreaId())) {
            carEnterLog.setAreaId(parkArmInfo.getTargetAreaId());
        }
        //批次号
        carEnterLog.setBatchNumber(carEnterLog.getId());

        ConfigCarType configCarType = this.getCarType(carEnterLog.getParkCode(), Integer.parseInt(carEnterLog.getCarTypeNo()));
        if (null != configCarType) {
            carEnterLog.setCarTypeId(configCarType.getId());
            carEnterLog.setCarTypeName(configCarType.getCarTypeName());
        }

        carEnterLog.setCarNature(JddConstant.MqttRecResultPushLog.IS_WHITE_ZERO);
        carEnterLog.setCarEnterTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        carEnterLog.setIsOut(JddConstant.IsOutType.ISOUT_ONE);
        carEnterLog.setCarType(MoblieControllerUtils.HAVEPLATE);
        carEnterLog.setCreateTime(new Date());
        //加上工作站名和岗亭人账户
        String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
        if (ObjectUtils.isNotEmpty(workUser)) {
            carEnterLog.setWorkName(parkArmInfo.getWorkStationName());
            carEnterLog.setSysUserName(workUser);
        }
        return carEnterLog;
    }

    /**
     * 获取识别类型对应的车辆类型
     *
     * @param parkCode 车场编号
     * @param type     车辆性质
     * @return 车辆类型
     */
    public ConfigCarType getCarType(String parkCode, int type) {
        QueryWrapper<ConfigCarType> configCarTypeQueryWrapper = new QueryWrapper<>();
        configCarTypeQueryWrapper.eq("park_code", parkCode);
        List<ConfigCarType> configCarTypeList = configCarTypeService.list(configCarTypeQueryWrapper);
        for (ConfigCarType configCarType : configCarTypeList) {
            List<String> list = Arrays.asList(configCarType.getCarTypeNo().split(","));
            if (list.contains(String.valueOf(type))) {
                return configCarType;
            }
        }
        return null;
    }
}

